package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"reflect"
	"strconv"
	"strings"
)

// MysqlConfig 具体配置;
type MysqlConfig struct {
	Address  string `ini:"address"`
	Port     int    `ini:"port"`
	Username string `ini:"username"`
	Password string `ini:"password"`
}

// Config 包含所有配置项的结构体;
type Config struct { // 结构体嵌套
	MysqlConfig MysqlConfig `ini:"mysql"`
}

func loadIni(fileName string, target interface{}) (err error) {
	// 1. 参数校验：是否为结构体指针(需在函数内对其修改键值对)
	t := reflect.TypeOf(target)
	if t.Kind() != reflect.Ptr {
		err = errors.New("target params should be a pointer") //实例化一个error类型的值，// 这种生成的错误不会打印在终端
		return
	}
	if t.Elem().Kind() != reflect.Struct {
		err = errors.New("target params should be a struct pointer") //实例化一个error类型的值
	}

	// 2. 读文件，一行一行读：区分注释、[]节、键值对
	// 优化：一次性读取得到字节类型数据，就关闭文件，不用一直处于打开状态
	b, err := ioutil.ReadFile(fileName)
	if err != nil {
		return
	}
	lineSlice := strings.Split(string(b), "\r\n") //装为字符串再切割成切片
	var structName string
	for index, line := range lineSlice {
		line = strings.TrimSpace(line) // 1.去掉前后空格，也确保不会有空行
		if len(line) == 0 {
			continue
		}
		if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { // 2.注释
			continue
		}
		if strings.HasPrefix(line, "[") { // 3.节
			sectionName := strings.TrimSpace(line[1 : len(line)-1]) //去掉前后[]
			if line[0] != '[' || line[len(line)-1] != ']' {
				err = fmt.Errorf("line: %d syntax error", index+1) // 这种生成的错误会打印在终端
				return
			}
			if len(sectionName) == 0 { // []内没写东西
				err = fmt.Errorf("line: %d syntax error", index+1)
				return
			}
			// 找到属于自己的节
			for i := 0; i < t.Elem().NumField(); i++ {
				field := t.Elem().Field(i)
				if sectionName == field.Tag.Get("ini") {
					structName = field.Name
					// fmt.Printf("找到节名为%s对应的嵌套结构体%s\n", sectionName, structName)
				}
			}
		} else { // 4.键值对
			if strings.Index(line, "=") == -1 || strings.HasPrefix(line, "=") {
				err = fmt.Errorf("line: %d syntax error 键值对不合法", index+1)
				return
			}
			v := reflect.ValueOf(target)
			sValue := v.Elem().FieldByName(structName) //拿到嵌套结构体的值信息
			sType := sValue.Type()                     //拿到嵌套结构体的类型信息
			if sType.Kind() != reflect.Struct {
				fmt.Printf("target中的 %s 字段应该是一个结构体\n", structName)
				return
			}
			// keyValue := strings.Split(line, "=")
			// key := strings.TrimSpace(keyValue[0])
			// value := strings.TrimSpace(keyValue[1])
			denghaoIdx := strings.Index(line, "=")
			key := strings.TrimSpace(line[:denghaoIdx])
			value := strings.TrimSpace(line[denghaoIdx+1:])

			// 每个字段的名和类型
			var fieldName string
			var fieldType reflect.StructField
			for i := 0; i < sValue.NumField(); i++ {
				field := sType.Field(i)
				fieldType = field
				if field.Tag.Get("ini") == key { //找到对应字段
					fieldName = field.Name
					break
				}
			}
			if len(fileName) == 0 {
				continue
			}
			fileObj := sValue.FieldByName(fieldName)

			// 通过strconv判断处理类型
			switch fieldType.Type.Kind() {
			case reflect.String:
				fileObj.SetString(value)
			case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
				var valueInt int64
				valueInt, err = strconv.ParseInt(value, 10, 64)
				if err != nil {
					err = fmt.Errorf("line: %d value type error", index+1)
					return
				}
				fileObj.SetInt(valueInt)
			case reflect.Bool:
				var valueBool bool
				valueBool, err = strconv.ParseBool(value)
				if err != nil {
					err = fmt.Errorf("line: %d value type error", index+1)
					return
				}
				fileObj.SetBool(valueBool)
			case reflect.Float32, reflect.Float64:
				var valueFloat float64
				valueFloat, err = strconv.ParseFloat(value, 64)
				if err != nil {
					err = fmt.Errorf("line: %d value type error", index+1)
					return
				}
				fileObj.SetFloat(valueFloat)
			}
			// fmt.Println(fieldName, fileObj, fieldType.Type.Kind())
		}
	}
	return
}
func main() {
	var c Config
	err := loadIni("./config.ini", &c)
	if err != nil {
		fmt.Printf("load ini failed， err: %v\n", err)
		return
	}
	fmt.Println(c)
}
